{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bKvX_88sNgXs"
      },
      "source": [
        "# AI Generated Characters for Learning and Wellbeing"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nO7671Y9oXiW"
      },
      "source": [
        "Website: https://www.media.mit.edu/projects/ai-generated-characters/overview/\n",
        "\n",
        "Paper: https://www.nature.com/articles/s42256-021-00417-9\n",
        "\n",
        "Github: https://github.com/mitmedialab/AI-generated-characters\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "![](https://drive.google.com/uc?export=view&id=17arRYqt6QyEjkj4-5eDrqRPcteTsbheO)\n"
      ],
      "metadata": {
        "id": "9M320pz78nl7"
      }
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2XSW0Yc6Nq-F"
      },
      "source": [
        "*This notebook is a combination of previous work on AI generated characters compiled into one easy to use pipeline that include [Siarohin et al.](https://github.com/AliaksandrSiarohin/first-order-model), [Prajwal et al.](https://github.com/Rudrabha/Wav2Lip), and [Corentin](https://github.com/CorentinJ/Real-Time-Voice-Cloning). Please go check out their amazing work.*"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tbvav0P_NNqj"
      },
      "source": [
        "**Licensed under the MIT License**\n",
        "\n",
        "\n",
        "Copyright (c) 2021 MIT Media Lab\n",
        "\n",
        "Permission is hereby granted, free of charge, to any person obtaining a copy\n",
        "of this software and associated documentation files (the \"Software\"), to deal\n",
        "in the Software without restriction, including without limitation the rights\n",
        "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n",
        "copies of the Software, and to permit persons to whom the Software is\n",
        "furnished to do so, subject to the following conditions:\n",
        "The above copyright notice and this permission notice shall be included in\n",
        "all copies or substantial portions of the Software.\n",
        "\n",
        "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
        "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n",
        "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n",
        "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n",
        "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n",
        "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n",
        "THE SOFTWARE."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "t-npGOwrGNhs",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "#@markdown #**Installation of libraries**\n",
        "# @markdown This cell will take a little while because it has to download several libraries.\n",
        "%cd \"/content\" \n",
        "import requests\n",
        "\n",
        "print(\"Downloading Packages\")\n",
        "# Character Images\n",
        "!gdown --id \"16HzQKA4e3vpLY8Em57WnE8UwIE591aF1\" -O \"/content/mona_lisa.png\" &> /dev/null\n",
        "!gdown --id \"1cgfFgzm4BrqKIkyspGib6u4ty5ReyeM_\" -O \"/content/einstein.png\" &> /dev/null\n",
        "!gdown --id \"10N3e5E0R1aYcLVmE_dmtMCSYVFGQLTeq\" -O \"/content/lincoln.png\" &> /dev/null\n",
        "!gdown --id \"1-BeSNGGjJADs5W-Rn6izAteuVzJcnhW1\" -O \"/content/nietzsche.png\" &> /dev/null\n",
        "!gdown --id \"1zPPUQ7xgbhnpVNl26J1Gl6rXlJ6g0rK7\" -O \"/content/sokrates.png\" &> /dev/null\n",
        "!gdown --id \"1mzzEdXEOohLcpr8L01JzOVbirEMJogni\" -O \"/content/van_gogh.png\" &> /dev/null\n",
        "\n",
        "# Face Cropping\n",
        "!wget \"https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt2.xml\" -O \"/content/haarcascade_frontalface_alt2.xml\" &> /dev/null\n",
        "\n",
        "# Wav2Lip\n",
        "!git clone \"https://github.com/Rudrabha/Wav2Lip.git\"\n",
        "!wget \"https://www.adrianbulat.com/downloads/python-fan/s3fd-619a316812.pth\" -O \"Wav2Lip/face_detection/detection/sfd/s3fd.pth\" &> /dev/null\n",
        "!gdown --id \"1IKhxXy0mplOpGFWLH9_uUhBoIplao8j0\" -O \"/content/Wav2Lip/checkpoints/wav2lip_gan.pth\" &> /dev/null\n",
        "\n",
        "# First-Order-Model\n",
        "!git clone \"https://github.com/AliaksandrSiarohin/first-order-model\"\n",
        "!gdown --id \"19d9ZJYAMsNNQZd4AzIWCw4sF1EaNYuJ3\" -O \"/content/first-order-model/vox-cpk.pth.tar\" &> /dev/null\n",
        "\n",
        "# Template Data\n",
        "#!gdown --id \"1Qod7I5hiK1nCPsHBqAdK6hoYZgNzQPHi\" -O \"driving_video_long.mp4\"\n",
        "!gdown --id \"1o2zD5xky8F6wZ21PkeG5KhJOlSdkeEpm\" -O \"driving_video.mp4\" &> /dev/null\n",
        "\n",
        "# Watermark\n",
        "url = 'https://raw.githubusercontent.com/mitmedialab/AI-generated-characters/main/gen.png'\n",
        "r = requests.get(url, allow_redirects=True) \n",
        "open('gen.png', 'wb').write(r.content)\n",
        "\n",
        "# Noise\n",
        "url = 'https://raw.githubusercontent.com/mitmedialab/AI-generated-characters/main/noise2.jpg'\n",
        "r = requests.get(url, allow_redirects=True)\n",
        "open('noise_2.png', 'wb').write(r.content)\n",
        "\n",
        "\n",
        "print(\"Installing required libraries\")\n",
        "!pip install -r Wav2Lip/requirements.txt -y &> /dev/null\n",
        "!pip uninstall tensorflow tensorflow-gpu -y &> /dev/null\n",
        "!pip install ffmpeg -y &> /dev/null\n",
        "!pip install https://github.com/tugstugi/dl-colab-notebooks/archive/colab_utils.zip &> /dev/null\n",
        "\n",
        "\n",
        "# General Functions\n",
        "print(\"Loading Libraries and functions\")\n",
        "import sys\n",
        "import numpy as np\n",
        "import ipywidgets as widgets\n",
        "from io import StringIO\n",
        "from IPython import get_ipython\n",
        "from IPython.display import display, Audio, clear_output\n",
        "from dl_colab_notebooks.audio import record_audio, upload_audio\n",
        "from scipy.io import wavfile\n",
        "\n",
        "class IpyExit(SystemExit):\n",
        "    \"\"\"\n",
        "    Exit Exception for IPython.\n",
        "    Exception temporarily redirects stderr to buffer.\n",
        "    \"\"\"\n",
        "    def __init__(self):\n",
        "        print(\"Error: Please only select one input. If you will not use text please leave text field empty.\")\n",
        "        sys.stderr = StringIO()\n",
        "\n",
        "    def __del__(self):\n",
        "        sys.stderr.close()\n",
        "        sys.stderr = sys.__stderr__  # restore from backup\n",
        "\n",
        "from google.colab import files\n",
        "def getLocalFiles():\n",
        "  uploaded = files.upload()\n",
        "  filename = next(iter(uploaded))\n",
        "  return filename\n",
        "\n",
        "\n",
        "# First-order-model\n",
        "import imageio\n",
        "import cv2\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "import matplotlib.animation as animation\n",
        "from skimage.transform import resize\n",
        "from IPython.display import HTML\n",
        "import warnings\n",
        "warnings.filterwarnings(\"ignore\")\n",
        "\n",
        "def _compute_embedding(audio):\n",
        "    display(Audio(audio, rate=SAMPLE_RATE, autoplay=True))\n",
        "    global embedding\n",
        "    embedding = None\n",
        "    embedding = encoder.embed_utterance(encoder.preprocess_wav(audio, SAMPLE_RATE))\n",
        "\n",
        "def _record_audio(b):\n",
        "  clear_output()\n",
        "  audio = record_audio(record_seconds, sample_rate=SAMPLE_RATE)\n",
        "  #_compute_embedding(audio)\n",
        "  display(Audio(audio, rate=SAMPLE_RATE, autoplay=True))\n",
        "  wavfile.write('driving_audio.wav', SAMPLE_RATE, (32767*audio).astype(np.int16))\n",
        "\n",
        "def _upload_audio(b):\n",
        "  clear_output()\n",
        "  audio = upload_audio(sample_rate=SAMPLE_RATE)\n",
        "  _compute_embedding(audio)\n",
        "\n",
        "def trim_img(img_src):\n",
        "  \n",
        "  import imutils\n",
        "\n",
        "  # Read the Input Image\n",
        "  img = cv2.imread(img_src)\n",
        "  img = imutils.resize(img, width=400)  \n",
        "\n",
        "  # Convert into grayscale\n",
        "  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
        "\n",
        "  # Trim to 400x400\n",
        "  face_cascade = cv2.CascadeClassifier('/content/haarcascade_frontalface_alt2.xml')\n",
        "  faces = face_cascade.detectMultiScale(gray, 1.1, 4)\n",
        "  try:\n",
        "    for (x, y, w, h) in faces:\n",
        "      extention = 40\n",
        "      faces = img[y-extention:y + h+extention, x-extention:x + w + extention]\n",
        "      cv2.imwrite('/content/img_trimmed.png', faces)\n",
        "  except:\n",
        "    print(\"Error: Face takes too much space on image. Try a different image, or trim it yourself to 400x400.\")\n",
        "\n",
        "  return \"/content/img_trimmed.png\"\n",
        "\n",
        "\n",
        "def animate_video(img_filename, vid_filename):\n",
        "    %cd /content/first-order-model/\n",
        "    \n",
        "    from demo import make_animation\n",
        "    from demo import load_checkpoints\n",
        "    from skimage import img_as_ubyte\n",
        "\n",
        "    source_image = imageio.imread(img_filename)\n",
        "    driving_video = imageio.mimread(vid_filename, fps=30, memtest=False) \n",
        "\n",
        "    # Resize image and video to 256x256\n",
        "    source_image = resize(source_image, (256, 256))[..., :3]\n",
        "    driving_video = [resize(frame, (256, 256))[..., :3] for frame in driving_video]\n",
        "\n",
        "    # Load Model\n",
        "    generator, kp_detector = load_checkpoints(config_path='config/vox-256.yaml', checkpoint_path='/content/first-order-model/vox-cpk.pth.tar')\n",
        "\n",
        "    # Make Animation\n",
        "    predictions = make_animation(source_image, driving_video, generator, kp_detector, relative=True,\n",
        "                                adapt_movement_scale=False)\n",
        "    #save resulting video\n",
        "    imageio.mimsave('/content/vidvid.mp4', [img_as_ubyte(frame) for frame in predictions], fps=30)\n",
        "\n",
        "    %cd /content\n",
        "\n",
        "\n",
        "def tracability(video_filename):\n",
        "  import moviepy.editor as mp\n",
        "\n",
        "  video = mp.VideoFileClip(video_filename)\n",
        "\n",
        "  machine = (mp.ImageClip('/content/noise_2.png')\n",
        "    .set_duration(video.duration)\n",
        "    .set_opacity(.05)\n",
        "    .resize(height = 552) #\n",
        "    .margin(right = 0, top = 0, opacity = 1.0)\n",
        "    .set_pos((\"center\", \"center\")))\n",
        "  \n",
        "  human = (mp.ImageClip('/content/gen.png')\n",
        "   .set_duration(video.duration)\n",
        "   .resize(height = 50) #\n",
        "   .margin(right = 0, top = 0, opacity = 1.0)\n",
        "   .set_pos((\"left\", \"bottom\")))\n",
        "\n",
        "  final = mp.CompositeVideoClip([video, machine, human])\n",
        "  final.write_videofile(\"/content/marked.mp4\")\n",
        "\n",
        "print(\"Succesfully Finished Installing Libraries\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rEvjGeswFb1Z",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "#@markdown #**Choose Character**\n",
        "\n",
        "# TO DO: Show Images of Characters one can choose.\n",
        "\n",
        "# @markdown Choose the character which you want to animate. If you have any requests for new characters to animate, please let us know here: patpat@mit.edu\n",
        "character = 'Mona Lisa' #@param [\"Van Gogh\", \"Mona Lisa\", \"Einstein\", \"Lincoln\", \"Nietzsche\", \"Sokrates\", \"Upload Your Own\"]\n",
        "print(f\"{character} selected.\")\n",
        "\n",
        "if character == \"Upload Your Own\":\n",
        "  character_img = \"/content/\"+getLocalFiles()\n",
        "  if cv2.imread(character_img).shape[0] != cv2.imread(character_img).shape[1]:\n",
        "    print(\"Cropping uploaded image\")\n",
        "    character_img = trim_img(character_img)\n",
        "\n",
        "else:\n",
        "  character = character.lower().replace(\" \", \"_\") # make lowercase and remove spacing\n",
        "  character_img = \"/content/\"+character+\".png\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xGIhE54sFPXG",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "#@markdown #**Choose Inputs**\n",
        "# @markdown Please select one of the available inputs. Leave the text field empty if you want to animate the character with audio or video.\n",
        "\n",
        "\n",
        "#Welcome. Today we will learn about the Theory of Relativity. I first came up with this method when...\n",
        "text = \"\" #@param {type:\"string\"}\n",
        "#@markdown --\n",
        "audio = True #@param {type:\"boolean\"}\n",
        "#@markdown * Either record audio from microphone or upload audio from file (.mp3 or .wav) \n",
        "record_or_upload = \"Record\" #@param [\"Record\", \"Upload (.mp3 or .wav)\"]\n",
        "record_seconds =  5#@param {type:\"number\", min:1, max:10, step:1}\n",
        "#@markdown --\n",
        "video = False #@param {type:\"boolean\"}\n",
        "\n",
        "if text != \"\" and audio or text !=\"\" and video or audio and video:\n",
        "  raise IpyExit\n",
        "\n",
        "\n",
        "if video:\n",
        "  print(\"Please upload the video you wish to drive the animation with:\\n\")\n",
        "  video_driver = \"/content/\"+getLocalFiles()\n",
        "\n",
        "  #to do: make sure only supported video formats can be uploaded\n",
        "\n",
        "elif audio:\n",
        "\n",
        "  SAMPLE_RATE = 22050\n",
        "  embedding = None\n",
        "\n",
        "  if record_or_upload == \"Record\":\n",
        "    print(\"Please record the audio you wish to drive the animation with. Remember to enable your microphone in Chrome:\\n\")\n",
        "    button = widgets.Button(description=\"Record Your Voice\")\n",
        "    button.on_click(_record_audio) \n",
        "    display(button)\n",
        "    audio_driver = \"/content/driving_audio.wav\"\n",
        "  else:\n",
        "    print(\"Please upload the audio you wish to drive the animation with:\\n\")\n",
        "    audio_driver = \"/content/\"+getLocalFiles()\n",
        "  video_driver = \"/content/driving_video.mp4\"\n",
        "\n",
        "elif text:\n",
        "  print(\"Text is currently unsupported but will be soon.. Please use either audio or video inputs for now.\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Orsq_D2RLvo2",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "from numpy.core import memmap\n",
        "import shutil\n",
        "\n",
        "\n",
        "#@markdown #**Generate Character**\n",
        "#@markdown This is likely to take a while depending on the length of your driving video. First we generate the movements of the character using the first-order-model approach, and then, if audio or text was given as input, we either synthesize audio from or use the audio provided to make the character lipsymc it using Wav2Lip.\n",
        "!cd /content/\n",
        "print(\"Animating Character with Driving Video: This might take a few minutes..\")\n",
        "animate_video(character_img, video_driver) # variables are only for showing HTML video\n",
        "final_video_driver = \"/content/vidvid.mp4\"\n",
        "\n",
        "if text != \"\":\n",
        "  print(\"Generating speech from text\")\n",
        "  # generate audio\n",
        "  #audio_driver = _GENERATED AUDIO.wav_\n",
        "  audio = True\n",
        "\n",
        "if audio:\n",
        "  print(\"Lipsyncing Character with Audio\")\n",
        "  # Using Wav2Lip\n",
        "  %cd /content/Wav2Lip\n",
        "  !python inference.py --checkpoint_path \"/content/Wav2Lip/checkpoints/wav2lip_gan.pth\" --face $final_video_driver --audio $audio_driver &> /dev/null\n",
        "  %cd /content\n",
        "  final_video_driver = \"/content/Wav2Lip/results/result_voice.mp4\"\n",
        "else:\n",
        "  audio_driver = \"/content/driver.wav\"\n",
        "  !ffmpeg -i $video_driver -q:a 0 -map 0:a \"/content/driver.wav\" -y &> /dev/null\n",
        "  !ffmpeg -i $final_video_driver -i $audio_driver -c:v copy -c:a aac merged.mp4 -y &> /dev/null\n",
        "  final_video_driver = \"merged.mp4\"\n",
        "\n",
        "# Traceability\n",
        "tracability(final_video_driver)\n",
        "final_video_driver = \"marked.mp4\"\n",
        "!ffmpeg -i $final_video_driver -i $audio_driver final_generated.mp4 -y &> /dev/null\n",
        "!ffmpeg -i $final_video_driver ai_generated_character.mp4 -y &> /dev/null\n",
        "final_video_driver = \"ai_generated_character.mp4\"\n",
        "\n",
        "# display result\n",
        "from IPython.display import HTML\n",
        "from base64 import b64encode\n",
        "mp4 = open(\"/content/final_generated.mp4\",'rb').read()\n",
        "data_url = \"data:video/mp4;base64,\" + b64encode(mp4).decode()\n",
        "HTML(\"\"\"\n",
        "<video width=400 controls>\n",
        "      <source src=\"%s\" type=\"video/mp4\">\n",
        "</video>\n",
        "\"\"\" % data_url)"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "#@markdown ### **Download the Generated Video**\n",
        "#@markdown Run this cell to download your generated video. If you wish to change your AI-generated character or the input, please go back to that cell and repeat the same process. You can skip the **Installation of libraries** section.\n",
        "\n",
        "from google.colab import files\n",
        "files.download(final_video_driver)"
      ],
      "metadata": {
        "cellView": "form",
        "id": "vSRqXk10zCDw"
      },
      "execution_count": null,
      "outputs": []
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "machine_shape": "hm",
      "name": "AI_Generated_Characters.ipynb",
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}